package edu.whu.rose.clock.ssql.graphindex;

import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;

import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.DatabaseException;

import edu.whu.rose.clock.ssql.binding.GraphIndexItemBinding;

public class GraphIndexCache {
	
	private HashMap<String, GraphIndexItem> cache;  // smaller than 50000
	private HashMap<String, GraphIndexItem> cache2; // smaller than 100000
	private HashMap<String, GraphIndexItem> cache3; // smaller than 500000
	private HashMap<String, GraphIndexItem> cache4; // smaller than 1000000
	private HashMap<String, GraphIndexItem> cache5; // larger than 1000000
	
	private long capacity;
	private GraphIndexDB gidb;
	private HashSet<String> stored;
	private long size;
	
	private HashSet<String> leastVisited;           // visited once
	private HashSet<String> leastVisited2;          // visited twice
	private HashSet<String> leastVisited3;          // visited three times
	private HashSet<String> leastVisited4;          // visited four times
	private HashSet<String> leastVisited5;          // visited five times
	
	private long visit;
	private long hit;
	
	public GraphIndexCache(GraphIndexDB gidb, long capacity) {
		cache = new HashMap<String, GraphIndexItem>();
		cache2 = new HashMap<String, GraphIndexItem>();
		cache3 = new HashMap<String, GraphIndexItem>();
		cache4 = new HashMap<String, GraphIndexItem>();
		cache5 = new HashMap<String, GraphIndexItem>();
		
		this.gidb = gidb;
		this.capacity = capacity;
		this.stored = new HashSet<String>();
		
		leastVisited = new HashSet<String>();
		leastVisited2 = new HashSet<String>();
		leastVisited3 = new HashSet<String>();
		leastVisited4 = new HashSet<String>();
		leastVisited5 = new HashSet<String>();
	}

	public void insert(String lcKey, int[] subgraphs/*, int[] sgkns*/, int vertex) throws UnsupportedEncodingException, DatabaseException {	
		GraphIndexItem data = cache.get(lcKey);
		if (data != null) {
			long before = data.getCapacity();
			data.add(subgraphs/*, sgkns*/, vertex);
			long after = data.getCapacity();
			size += after - before;
			if (after > 50000) {
				cache2.put(lcKey, data);
				cache.remove(lcKey);
			}
			else if (after > 100000) {
				cache3.put(lcKey, data);
				cache.remove(lcKey);
			}
			else if (after > 500000) {
				cache4.put(lcKey, data);
				cache.remove(lcKey);
			}
			else if (after > 1000000) {
				cache5.put(lcKey, data);
				cache.remove(lcKey);
			}
			if (leastVisited.contains(lcKey)) {
				leastVisited.remove(lcKey);
				leastVisited2.add(lcKey);
			}
			else if (leastVisited2.contains(lcKey)) {
				leastVisited2.remove(lcKey);
				leastVisited3.add(lcKey);
			}
			else if (leastVisited3.contains(lcKey)) {
				leastVisited3.remove(lcKey);
				leastVisited4.add(lcKey);
			}
			else if (leastVisited4.contains(lcKey)) {
				leastVisited4.remove(lcKey);
				leastVisited5.add(lcKey);
			}
			else if (leastVisited5.contains(lcKey)) {
				leastVisited5.remove(lcKey);
				size -= 2 * lcKey.length();
			}
			visit++;
			hit++;
		}
		else {
			data = cache2.get(lcKey);
			if (data != null) {
				long before = data.getCapacity();
				data.add(subgraphs/*, sgkns*/, vertex);
				long after = data.getCapacity();
				size += after - before;
				if (after > 100000) {
					cache3.put(lcKey, data);
					cache2.remove(lcKey);
				}
				else if (after > 500000) {
					cache4.put(lcKey, data);
					cache2.remove(lcKey);
				}
				else if (after > 1000000) {
					cache5.put(lcKey, data);
					cache2.remove(lcKey);
				}
				if (leastVisited.contains(lcKey)) {
					leastVisited.remove(lcKey);
					leastVisited2.add(lcKey);
				}
				else if (leastVisited2.contains(lcKey)) {
					leastVisited2.remove(lcKey);
					leastVisited3.add(lcKey);
				}
				else if (leastVisited3.contains(lcKey)) {
					leastVisited3.remove(lcKey);
					leastVisited4.add(lcKey);
				}
				else if (leastVisited4.contains(lcKey)) {
					leastVisited4.remove(lcKey);
					leastVisited5.add(lcKey);
				}
				else if (leastVisited5.contains(lcKey)) {
					leastVisited5.remove(lcKey);
					size -= 2 * lcKey.length();
				}
				visit++;
				hit++;
			}
			else {
				data = cache3.get(lcKey);
				if (data != null) {
					long before = data.getCapacity();
					data.add(subgraphs/*, sgkns*/, vertex);
					long after = data.getCapacity();
					size += after - before;
					if (after > 500000) {
						cache4.put(lcKey, data);
						cache3.remove(lcKey);
					}
					else if (after > 1000000) {
						cache5.put(lcKey, data);
						cache3.remove(lcKey);
					}
					if (leastVisited.contains(lcKey)) {
						leastVisited.remove(lcKey);
						leastVisited2.add(lcKey);
					}
					else if (leastVisited2.contains(lcKey)) {
						leastVisited2.remove(lcKey);
						leastVisited3.add(lcKey);
					}
					else if (leastVisited3.contains(lcKey)) {
						leastVisited3.remove(lcKey);
						leastVisited4.add(lcKey);
					}
					else if (leastVisited4.contains(lcKey)) {
						leastVisited4.remove(lcKey);
						leastVisited5.add(lcKey);
					}
					else if (leastVisited5.contains(lcKey)) {
						leastVisited5.remove(lcKey);
						size -= 2 * lcKey.length();
					}
					visit++;
					hit++;
				}
				else {
					data = cache4.get(lcKey);
					if (data != null) {
						long before = data.getCapacity();
						data.add(subgraphs/*, sgkns*/, vertex);
						long after = data.getCapacity();
						size += after - before;
						if (after > 1000000) {
							cache5.put(lcKey, data);
							cache4.remove(lcKey);
						}
						if (leastVisited.contains(lcKey)) {
							leastVisited.remove(lcKey);
							leastVisited2.add(lcKey);
						}
						else if (leastVisited2.contains(lcKey)) {
							leastVisited2.remove(lcKey);
							leastVisited3.add(lcKey);
						}
						else if (leastVisited3.contains(lcKey)) {
							leastVisited3.remove(lcKey);
							leastVisited4.add(lcKey);
						}
						else if (leastVisited4.contains(lcKey)) {
							leastVisited4.remove(lcKey);
							leastVisited5.add(lcKey);
						}
						else if (leastVisited5.contains(lcKey)) {
							leastVisited5.remove(lcKey);
							size -= 2 * lcKey.length();
						}
						visit++;
						hit++;
					}
					else {
						data = cache5.get(lcKey);
						if (data != null) {
							long before = data.getCapacity();
							data.add(subgraphs/*, sgkns*/, vertex);
							long after = data.getCapacity();
							size += after - before;
							if (leastVisited.contains(lcKey)) {
								leastVisited.remove(lcKey);
								leastVisited2.add(lcKey);
							}
							else if (leastVisited2.contains(lcKey)) {
								leastVisited2.remove(lcKey);
								leastVisited3.add(lcKey);
							}
							else if (leastVisited3.contains(lcKey)) {
								leastVisited3.remove(lcKey);
								leastVisited4.add(lcKey);
							}
							else if (leastVisited4.contains(lcKey)) {
								leastVisited4.remove(lcKey);
								leastVisited5.add(lcKey);
							}
							else if (leastVisited5.contains(lcKey)) {
								leastVisited5.remove(lcKey);
								size -= 2 * lcKey.length();
							}
							visit++;
							hit++;
						}
						else {
							long least = 2 * lcKey.length() + 4 * subgraphs.length + 4;
							String s = null;
							while (size + least > capacity) {
								if (leastVisited.size() > 0) {
									Iterator<String> it = leastVisited.iterator();
									s = it.next();
									it.remove();
									size -= 2 * s.length();
								}
								else {
									if (leastVisited2.size() > 0) {
										Iterator<String> it2 = leastVisited2.iterator();
										s = it2.next();
										it2.remove();
										size -= 2 * s.length();
									}
									else {
										if (leastVisited3.size() > 0) {
											Iterator<String> it3 = leastVisited3.iterator();
											s = it3.next();
											it3.remove();
											size -= 2 * s.length();
										}
										else {
											if (leastVisited4.size() > 0) {
												Iterator<String> it4 = leastVisited4.iterator();
												s = it4.next();
												it4.remove();
												size -= 2 * s.length();
											}
											else {
												if (leastVisited5.size() > 0) {
													Iterator<String> it5 = leastVisited5.iterator();
													s = it5.next();
													it5.remove();
													size -= 2 * s.length();
												}
												else {
													if (cache.size() > 0) {
														Iterator<String> it6 = cache.keySet().iterator();
														s = it6.next();
													}
													else if (cache2.size() > 0) {
														Iterator<String> it6 = cache2.keySet().iterator();
														s = it6.next();
													}
													else if (cache3.size() > 0) {
														Iterator<String> it6 = cache3.keySet().iterator();
														s = it6.next();
													}
													else if (cache4.size() > 0) {
														Iterator<String> it6 = cache4.keySet().iterator();
														s = it6.next();
													}
													else {
														Iterator<String> it6 = cache5.keySet().iterator();
														while (it6.hasNext()) {
															s = it6.next();
															GraphIndexItem gii = cache5.get(s);
															if (gii.getCapacity() < 5000000) {
																break;
															}
														}
													}
												}
											}
										}
									}
								}
								remove(s);
							}
							
							if (stored.contains(lcKey)) {
								data = getFromDB(lcKey);
								stored.remove(lcKey);
								if (data != null) {
									data.add(subgraphs/*, sgkns*/, vertex);
									if (data.getCapacity() < 50000) {
										cache.put(lcKey, data);
									}
									else if (data.getCapacity() < 100000) {
										cache2.put(lcKey, data);
									}
									else if (data.getCapacity() < 500000) {
										cache3.put(lcKey, data);
									}
									else if (data.getCapacity() < 1000000) {
										cache4.put(lcKey, data);
									}
									else {
										cache5.put(lcKey, data);
									}
									
									if (data.getCapacity() > 5000000) {
										System.err.println("reload overlarge");
									}
									
									size += data.getCapacity();
									visit++;
								}
								else {
									System.err.println("Error");
								}
							}
							else {
								data = new GraphIndexItem();
								data.add(subgraphs/*, sgkns*/, vertex);
								if (data.getCapacity() < 50000) {
									cache.put(lcKey, data);
								}
								else if (data.getCapacity() < 100000) {
									cache2.put(lcKey, data);
								}
								else if (data.getCapacity() < 500000) {
									cache3.put(lcKey, data);
								}
								else if (data.getCapacity() < 1000000) {
									cache4.put(lcKey, data);
								}
								else {
									cache5.put(lcKey, data);
								}
								size += 2 * lcKey.length() + data.getCapacity();
								leastVisited2.add(lcKey);
								size += 2 * lcKey.length();
							}
						}
					}
				}
			}
		}
		
		
		
		
//		GraphIndexItem data = cache.get(lcKey);
//		if (data != null) {
//			long before = data.getCapacity();
//			data.add(subgraphs/*, sgkns*/, vertex);
//			long after = data.getCapacity();
////			if (after > 1000000) {
////				System.out.println("found overlarge");
////			}
//			size += after - before;
////			leastVisited.remove(lcKey);
//			
//			if (leastVisited.contains(lcKey)) {
//				leastVisited.remove(lcKey);
//				leastVisited2.add(lcKey);
//			}
//			else if (leastVisited2.contains(lcKey)) {
//				leastVisited2.remove(lcKey);
//				leastVisited3.add(lcKey);
//			}
//			else if (leastVisited3.contains(lcKey)) {
//				leastVisited3.remove(lcKey);
//			}
//			
//			size -= 2 * lcKey.length();
//			visit++;
//			hit++;
//		}
//		else {
//			long least = 2 * key.length() + 4 * subgraphs.length + 4;
//			if (size + least > capacity) {
//				String s = null;
////				Iterator<String> it = leastVisited.iterator();
////				while (size + least > capacity) {
////					if (it.hasNext()) {
////						s = it.next();
////						it.remove();
////						size -= 2 * s.length();
////					}
////					else {
////						Iterator<String> it2 = cache.keySet().iterator();
////						s = it2.next();
////					}
////					remove(s);
////				}
//				while (size + least > capacity) {
//					
//					if (leastVisited.size() > 0) {
//						Iterator<String> it = leastVisited.iterator();
//						s = it.next();
//						it.remove();
//						size -= 2 * s.length();
//					}
//					else {
//						
//						if (leastVisited2.size() > 0) {
//							Iterator<String> it2 = leastVisited2.iterator();
//							s = it2.next();
//							it2.remove();
//							size -= 2 * s.length();
//						}
//						else {
//							
//							if (leastVisited3.size() > 0) {
//								Iterator<String> it3 = leastVisited3.iterator();
//								s = it3.next();
//								it3.remove();
//								size -= 2 * s.length();
//							}
//							else {
//								Iterator<String> it4 = cache.keySet().iterator();
//								boolean b = true;
//								while (it4.hasNext()) {
//									
//									s = it4.next();
//									GraphIndexItem gii = cache.get(s);
//									if (gii.getCapacity() < 100000) {
//										b = false;
//										break;
//									}
//								}
//								if (b) System.err.println("no more small item");
//							}
//						}
//					}
//					remove(s);
//				}
//				
//				
//			}
//			
//			if (stored.contains(lcKey)) {
//				data = getFromDB(lcKey);
//				stored.remove(lcKey);
//				if (data != null) {
//					data.add(subgraphs/*, sgkns*/, vertex);
//					cache.put(lcKey, data);
//					if (data.getCapacity() > 1000000) {
//						System.err.println("reload overlarge");
//					}
//					size += data.getCapacity();
//					visit++;
//				}
//				else {
//					System.err.println("Error");
//				}
//			}
//			else {
//				data = new GraphIndexItem();
//				data.add(subgraphs/*, sgkns*/, vertex);
//				cache.put(lcKey, data);
//				size += 2 * lcKey.length() + data.getCapacity();
////				leastVisited.add(lcKey);
//				
//				leastVisited2.add(lcKey);
//				
//				size += 2 * lcKey.length();
//			}
//			
////			queue.insertAtEnd(new StrLinkedNode(lcKey));
//			
//		}
	}
	
//	private void addData(GraphIndexItem data, int[] subgraphs, int vertex) {
//		long before = data.getCapacity();
//		data.add(subgraphs/*, sgkns*/, vertex);
//		long after = data.getCapacity();
//		size += after - before;
//		
//	}
	
	private void remove(String keyword) throws UnsupportedEncodingException, DatabaseException {
		GraphIndexItem gii = cache.remove(keyword);
		if (gii != null) {
			size -= gii.getCapacity();
			putIntoDB(keyword, gii);
			stored.add(keyword);
		}
		else {
			gii = cache2.remove(keyword);
			if (gii != null) {
				size -= gii.getCapacity();
				putIntoDB(keyword, gii);
				stored.add(keyword);
			}
			else {
				gii = cache3.remove(keyword);
				if (gii != null) {
					size -= gii.getCapacity();
					putIntoDB(keyword, gii);
					stored.add(keyword);
				}
				else {
					gii = cache4.remove(keyword);
					if (gii != null) {
						size -= gii.getCapacity();
						putIntoDB(keyword, gii);
						stored.add(keyword);
					}
					else {
						gii = cache5.remove(keyword);
						if (gii != null) {
							size -= gii.getCapacity();
							putIntoDB(keyword, gii);
							stored.add(keyword);
						}
						else {
							System.err.println("error");
						}
					}
				}
			}
		}
		size += 2 * keyword.length();
		
		
//		GraphIndexItem gii = cache.remove(keyword);
//		size -= gii.getCapacity();
//		putIntoDB(keyword, gii);
//		stored.add(keyword);
	}
	
	public GraphIndexItem getFromDB(String key) throws UnsupportedEncodingException, DatabaseException {
		GraphIndexItem result = gidb.search(key);
		return result;
	}
	
	public void putIntoDB(String key, GraphIndexItem data) throws UnsupportedEncodingException, DatabaseException {
		DatabaseEntry keyword = new DatabaseEntry(key.getBytes("UTF-8"));
		DatabaseEntry list = new DatabaseEntry();
		GraphIndexItemBinding giib = new GraphIndexItemBinding();
		giib.objectToEntry(data, list);
		
		gidb.put(keyword, list);
	}
	
	public void flush() throws UnsupportedEncodingException, DatabaseException {
		Iterator<String> it = cache.keySet().iterator();
		int counter = 0;
		Date stime = new Date();
		Date etime = null;
		while (it.hasNext()) {
			String key = it.next();
			if (key.equals("database")) {
				System.err.println();
			}
			GraphIndexItem data = cache.get(key);
			this.putIntoDB(key, data);
			counter++;
			if (counter % 100 == 0) {
				etime = new Date();
				System.out.println(counter + "........................." + (etime.getTime() - stime.getTime()) + "ms");
				stime = etime;
			}
		}
	}
	
	public void statistic() {
		System.out.println("Cache Hit Rate: " + (100 * (double)hit / ((double)visit)) + "%");
	}

}
